#include "tiltwidget.h"

TiltWidget::TiltWidget(DrunkSceneModel *m, QGraphicsScene *s,Noise *noise, QWidget *parent) :
    QGraphicsView(s, parent)
{
    setFixedSize(960, 540);
    setOptimizationFlag(QGraphicsView::DontAdjustForAntialiasing);
    setRenderHint(QPainter::Antialiasing, true);
    setRenderHint(QPainter::SmoothPixmapTransform, true);
    model = m;

    scene = s;
    setMouseTracking(true);

    loadWalkcycleAnimation();
    buildCharacter();
    buildMap();

    setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);

    setBackgroundBrush(Qt::black);
    setViewportUpdateMode(QGraphicsView::FullViewportUpdate);

    loadBackgroundAnimation();
    loadObjectGraphics();

    standUpProgress = 0.0;
    gameCondition = GAME_PLAYING;

    this->noise = noise;

    connect(model, SIGNAL(standCountdown(int)), this, SLOT(updateStandUpProgress(int)));
}

void TiltWidget::loadObjectGraphics()
{
    objectsLoRes = new QMap<QString, QImage>();
    objectsHiRes = new QMap<QString, QImage>();

    objectsLoRes->insert("obstacle_crate_wood", QImage("objects/lores/obstacle_crate.png"));
    objectsLoRes->insert("powerup_energydrink", QImage("objects/lores/powerup_energydrink.png"));
    objectsLoRes->insert("banana", QImage("objects/lores/powerdown_banana.png"));

    objectsHiRes->insert("obstacle_crate_wood", QImage("objects/hires/obstacle_crate.png"));
    objectsHiRes->insert("powerup_energydrink", QImage("objects/hires/powerup_energydrink.png"));
    objectsHiRes->insert("banana", QImage("objects/hires/powerdown_banana.png"));
}

void TiltWidget::setModel(DrunkSceneModel *m)
{
    model = m;
    mapDisplay->setLength(m->getMapLength());
    connect(model, SIGNAL(standCountdown(int)), this, SLOT(updateStandUpProgress(int)));

    standUpProgress = 0.0;
    gameCondition = GAME_PLAYING;
}

void TiltWidget::buildMap()
{
    mapDisplay = new MapDisplayItem(model->getMapLength(), 0);
    mapDisplay->setSize(20, height() - 20);

    mapDisplay->setPos(0 - width() / 2 + 10, 0 - height() / 2 + 10);
    mapDisplay->setVisible(true);
    scene->addItem(mapDisplay);
}

void TiltWidget::buildCharacter()
{
    legs = new QGraphicsPixmapItem(0, scene);
    legs->setPixmap(QPixmap("character/walkcycle_000000.png"));
    legs->setScale(1.0);
    legs->setOffset(0.0 - legs->boundingRect().width() / 2.0,
                    20.0 - legs->boundingRect().height());
    legs->setPos(0.0, LEGS_Y_POSITION);
    legs->setTransformationMode(Qt::SmoothTransformation);

    torso = new QGraphicsPixmapItem(legs);
    torso->setPixmap(QPixmap("character/torso.png"));
    torso->setScale(1.0);
    torso->setOffset(0.0 - torso->boundingRect().width() / 2.0,
                    0.0 - torso->boundingRect().height());
    torso->setPos(3.0, 0.0 - legs->boundingRect().height() + 55.0);
    legs->setTransformationMode(Qt::SmoothTransformation);

    head = new QGraphicsPixmapItem(torso);
    head->setPixmap(QPixmap("character/head.png"));
    head->setScale(1.0);
    head->setOffset(-24.0, -73.0);
    head->setPos(0.0, -105.0);
    head->setFlag(QGraphicsItem::ItemStacksBehindParent);
    head->setTransformationMode(Qt::SmoothTransformation);

    leftArm = new QGraphicsPixmapItem(torso);
    leftArm->setPixmap(QPixmap("character/left_arm.png"));
    leftArm->setScale(1.0);
    leftArm->setOffset(-62.0, -17.0);
    leftArm->setPos(48.0 - torso->boundingRect().width(),
                    34.0 - torso->boundingRect().height());
    leftArm->setFlag(QGraphicsItem::ItemStacksBehindParent);
    leftArm->setTransformationMode(Qt::SmoothTransformation);

    leftForearm = new QGraphicsPixmapItem(leftArm);
    leftForearm->setPixmap(QPixmap("character/left_forearm.png"));
    leftForearm->setScale(1.0);
    leftForearm->setOffset(-84.0, -25.0);
    leftForearm->setPos(20.0 - leftArm->boundingRect().width(), 10.0);
    leftForearm->setFlag(QGraphicsItem::ItemStacksBehindParent);
    leftForearm->setTransformationMode(Qt::SmoothTransformation);

    rightArm = new QGraphicsPixmapItem(torso);
    rightArm->setPixmap(QPixmap("character/right_arm.png"));
    rightArm->setScale(1.0);
    rightArm->setOffset(-16.0, -17.0);
    rightArm->setPos(torso->boundingRect().width() - 48.0,
                    32.0 - torso->boundingRect().height());
    rightArm->setFlag(QGraphicsItem::ItemStacksBehindParent);
    rightArm->setTransformationMode(Qt::SmoothTransformation);

    rightForearm = new QGraphicsPixmapItem(rightArm);
    rightForearm->setPixmap(QPixmap("character/right_forearm.png"));
    rightForearm->setScale(1.0);
    rightForearm->setOffset(-13.0, -19.0);
    rightForearm->setPos(rightArm->boundingRect().width() - 22.0, 8.0);
    rightForearm->setFlag(QGraphicsItem::ItemStacksBehindParent);
    rightForearm->setTransformationMode(Qt::SmoothTransformation);
}

void TiltWidget::animateCharacter()
{
    switch(gameCondition)
    {
    case GAME_PLAYING:
        if(model->getIsStanding())
        {
            float userTilt = model->getInputAcc();
            userTilt *= 90.0;

            int frame = model->getYPosition() * WALKCYCLE_FRAME_SPEED;
            frame = frame % FRAMES_WALKCYCLE;
            legs->setPixmap(walkcycleImages[frame]);

            legs->setPos(model->getXPosition() / 5.0 * width() - 480.0,
                            LEGS_Y_POSITION);
            legs->setRotation(model->getTilt() * ROTATE_LEGS);
            torso->setRotation(model->getTilt() * ROTATE_TORSO + userTilt * ROTATE_TORSO_ALT);

            float torsoBob = noise->getMultiSineAt(model->getYPosition(), BOB_TORSO_WAVELENGTH, 2, 0.5, 0.75) * BOB_TORSO_AMPLITUDE;
            torso->setPos(3.0, 0.0 - legs->boundingRect().height() + 55.0 + torsoBob);

            float headBob = noise->getMultiSineAt(model->getYPosition(), BOB_HEAD_WAVELENGTH, 5, 0.571282, 0.79) * BOB_HEAD_AMPLITUDE;
            head->setPos(0.0, -105.0 + headBob);

            head->setRotation(userTilt * ROTATE_HEAD);
            leftArm->setRotation(userTilt * ROTATE_LEFT_ARM);
            leftForearm->setRotation(userTilt * ROTATE_LEFT_FOREARM + 20.0);

            float rightWeave = noise->getMultiSineAt(model->getYPosition(), 10.0, 7, 0.671282, 0.9) * 10.0;
            rightArm->setRotation(userTilt * ROTATE_RIGHT_ARM + 15.0 + (rightWeave * 0.5));
            rightForearm->setRotation(model->getTilt() * ROTATE_RIGHT_FOREARM - 100.0 + rightWeave);
        }
        else
        {
            if(model->getTilt() < 0.0)
            {
                // Animate a soft fall to the left
                legs->setRotation((legs->rotation() + TARGETL_LEGS) / 2.0);
                torso->setRotation((torso->rotation() + TARGETL_TORSO) / 2.0);
                leftArm->setRotation((leftArm->rotation() + TARGETL_LEFT_ARM) / 2.0);
                leftForearm->setRotation((leftForearm->rotation() + TARGETL_LEFT_FOREARM) / 2.0);
                head->setRotation((head->rotation() + TARGETL_HEAD) / 2.0);
                rightArm->setRotation((rightArm->rotation() + TARGETL_RIGHT_ARM) / 2.0);
                rightForearm->setRotation((rightForearm->rotation() + TARGETL_RIGHT_FOREARM) / 2.0);
            }
            else
            {
                // Animate a soft fall to the right
                legs->setRotation((legs->rotation() + TARGETR_LEGS) / 2.0);
                torso->setRotation((torso->rotation() + TARGETR_TORSO) / 2.0);
                leftArm->setRotation((leftArm->rotation() + TARGETR_LEFT_ARM) / 2.0);
                leftForearm->setRotation((leftForearm->rotation() + TARGETR_LEFT_FOREARM) / 2.0);
                head->setRotation((head->rotation() + TARGETR_HEAD) / 2.0);
                rightArm->setRotation((rightArm->rotation() + TARGETR_RIGHT_ARM) / 2.0);
                rightForearm->setRotation((rightForearm->rotation() + TARGETR_RIGHT_FOREARM) / 2.0);
            }
        }
        break;
    case GAME_WON:
        // Victory celebration
        legs->moveBy(0.0, -100.0);
        legs->setRotation(TARGETW_LEGS);
        torso->setRotation(TARGETW_TORSO);
        leftArm->setRotation(TARGETW_LEFT_ARM);
        leftForearm->setRotation(TARGETW_LEFT_FOREARM);
        head->setRotation(TARGETW_HEAD);
        rightArm->setRotation(TARGETW_RIGHT_ARM);
        rightForearm->setRotation(TARGETW_RIGHT_FOREARM);
        break;
    }
}

void TiltWidget::loadWalkcycleAnimation()
{
    for (int i = 0; i < FRAMES_WALKCYCLE; i++)
    {
        QString paddedNumber = QString("%1").arg(i, 6, 10, QChar('0'));
        QString filename("character/walkcycle_");
        filename.append(paddedNumber);
        filename.append(".png");
        walkcycleImages[i].load(filename);

    }
}

void TiltWidget::loadBackgroundAnimation()
{
    for (int i = 0; i < FRAMES_BACKGROUND; i++)
    {
        QString paddedNumber = QString("%1").arg(i, 6, 10, QChar('0'));
        QString filename("background/background_urban_A001_");
        filename.append(paddedNumber);
        filename.append(".jpg");
        backgroundImages[i].load(filename);
    }
}

void TiltWidget::drawBackground(QPainter *painter, const QRectF &rect)
{
    painter->setRenderHint(QPainter::SmoothPixmapTransform, true);
    painter->setRenderHint(QPainter::Antialiasing, true);

    painter->translate(0.0 - (width() / 2.0), 0.0 - (height() / 2.0));

    // Figure out correct background frame to display
    int frame = model->getYPosition() * 10;
    frame = frame % FRAMES_BACKGROUND;
    painter->drawImage(0, 0, backgroundImages[frame]);

    // If the goal is visible, draw the finish line
    float distToFinish = model->getMapLength() - model->getYPosition();
    if(distToFinish <= VISIBLE_DISTANCE)
    {
        QPointF leftSide = projectToScreenSpace(-3.5, distToFinish);
        QPointF rightSide = projectToScreenSpace(3.5, distToFinish);
        QPen finishLinePen(Qt::yellow);

        if(distToFinish > 1.0)
            finishLinePen.setWidthF(10.0 / distToFinish);
        else
            finishLinePen.setWidth(10.0);

        painter->setPen(finishLinePen);
        painter->drawLine(leftSide, rightSide);
    }

    // Draw the visible objects
    QLinkedList<SceneObject*> objs = model->getObjectsAtDistance(VISIBLE_DISTANCE);

    if (!objs.empty())
    {
        QLinkedListIterator<SceneObject*> iterator(objs);
        iterator.toBack();

        while (iterator.hasPrevious())
        {
            SceneObject* object = iterator.previous();
            QPointF objLeftPos = projectToScreenSpace(
                        object->getXPosition() - (object->getWidth() / 2.0) - 2.5,
                        object->getYPosition() - model->getYPosition());
            QPointF objRightPos = projectToScreenSpace(
                        object->getXPosition() + (object->getWidth() / 2.0) - 2.5,
                        object->getYPosition() - model->getYPosition());
            int objWidth = objRightPos.x() - objLeftPos.x();

            QString resId = object->getResourceId();

            if(resId.length() > 0)
            {
                if(objWidth > LOD_SWITCHOVER)
                {
                    if(objectsHiRes->contains(resId))
                    {
                        painter->drawImage(
                                    QRectF(objLeftPos.x(), objLeftPos.y() - objWidth,
                                           objWidth, objWidth), objectsHiRes->value(resId));
                    }
                }
                else
                {
                    if(objectsLoRes->contains(resId))
                    {
                        painter->drawImage(
                                    QRectF(objLeftPos.x(), objLeftPos.y() - objWidth,
                                           objWidth, objWidth), objectsLoRes->value(resId));
                    }
                }
            }
        }
    }


    // If the character has fallen down, draw a pie-style timer
    if (standUpProgress > 0.0)
    {
        QPen piePen(Qt::SolidLine);
        piePen.setWidth(1);
        piePen.setColor(Qt::green);

        int spanAngle = (int) ((1.0 - standUpProgress) * 5760.0);

        painter->setPen(piePen);
        painter->setBrush(Qt::green);
        painter->drawPie(width() / 2 - 20, height() / 2 - 20, 40, 40, 1440,
                        spanAngle);
    }

    // If the game is over, draw the corresponding message
    if(gameCondition == GAME_WON)
    {
        painter->setFont(QFont("sans serif", 24, QFont::Bold));
        painter->drawText(410,50, 800, 50, 0, "YOU WIN!");
    }
    else if(gameCondition == GAME_LOST)
    {
        painter->setFont(QFont("sans serif", 24, QFont::Bold));
        painter->drawText(410,50,800,50,0, "YOU LOSE");
    }
}

void TiltWidget::mouseMoveEvent(QMouseEvent *e)
{
    int x = e->x();
    float tiltVal = ((float) x / width() - 0.5) * 2.0;
    if(tiltVal >= -1.0 && tiltVal <= 1.0)
        model->setInput(tiltVal);
}

void TiltWidget::updateScreen() {
    animateCharacter();
    mapDisplay->updatePositions(model->getYPosition(), model->getOpponentsPosition());
    scene->update();
    update();
}

void TiltWidget::mousePressEvent(QMouseEvent *e)
{
}

void TiltWidget::setDifficulty(int dif) {
    float dDif = (float) dif / 100.0;
    model->setNavigationDifficulty(dDif);
}

void TiltWidget::updateStandUpProgress(int time)
{
    standUpProgress = time / 4000.0;
}

QPointF TiltWidget::projectToScreenSpace(float Ax, float Az)
{
    float Bz = 2.0;                                 // Distance from camera to image plane
    float scaleX = model->getMapWidth() / 2.0;      // Factor to scale x-axis by
    float scaleZ = 1.42;                            // Factor to scale depth by (used to sync background)
    float Ay = 1.0;                                 // Height of camera

    float Bx = (Ax / scaleX) * (Bz / (Az * scaleZ + Bz));
    Bx = (Bx + 1.0) * width() / 2.0;

    float By = Ay * (Bz / (Az * scaleZ + Bz));
    By = (By + 1.0) * height() / 2.0;

    return QPointF(Bx, By);
}

void TiltWidget::winGame(bool win, int time)
{
    if(win)
        gameCondition = GAME_WON;
    else
        gameCondition = GAME_LOST;
}

